package com.dcivision.dms.core;

import java.io.File;
import java.io.IOException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.impl.LogFactoryImpl;
import org.apache.lucene.document.Field;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.Hits;

import com.dcivision.dms.bean.DmsDocument;
import com.dcivision.dms.bean.DmsLocMaster;
import com.dcivision.dms.bean.DmsRoot;
import com.dcivision.dms.bean.DmsVersion;
import com.dcivision.dms.dao.DmsDocumentDAObject;
import com.dcivision.dms.dao.DmsDocumentDetailDAObject;
import com.dcivision.dms.dao.DmsLocMasterDAObject;
import com.dcivision.dms.dao.DmsRootDAObject;
import com.dcivision.framework.GlobalConstant;
import com.dcivision.framework.SessionContainer;
import com.dcivision.framework.Utility;
import com.dcivision.lucene.FieldNames;
import com.dcivision.lucene.FieldTypes;
import com.dcivision.lucene.IndexConverter;
import com.dcivision.lucene.IndexManager;

/**
 * This class is to checking error for dms records.
 * @author          Lee Lv
 * @company         DCIVision Limited
 * @creation date   11/11/2006
 * @version         $Revision: 1.6 $
 */
public class DmsChecker {
  
  public static final String REVISION = "$Revision: 1.6 $";
  
  private Log log = new LogFactoryImpl().getInstance(this.getClass());
  
  public static final String LIST_NO_BASE_PATH = "lstNoBaseRoot";
  
  public static final String LIST_NO_PHYSICAL_FILE = "lstNoPhysicalFile";
  
  public static final String LIST_SHORTCUT_NO_TARGET = "lstShortcutNoTarget";
  
  public static final String LIST_DUPLICATE_NAME = "lstDuplicateName";
  
  public static final String LIST_NO_LUCENE_INDEX = "lstNoLuceneIndex";
  
  public static final String LIST_NO_LUCENE_FULLTEXT = "lstNoLuceneFullText";
  
  public static final String LIST_MULTI_LUCENE_INDEX = "lstMultiLuceneIndex";
  
  public static final String LIST_PROFILE_MISSING = "lstProfileMissing";
  
  private SessionContainer sessionContainer = null;
  
  private Connection conn = null;
  
  private IndexManager indexManager = null;
  
  private DmsDocumentDAObject dmsDocumentDAO = null;
  
  private DmsDocumentDetailDAObject dmsDocumentDetailDAO = null;
  
  private DmsLocMasterDAObject dmsLocMasterDAO = null;
  
  private DmsRootDAObject dmsRootDAO = null;
  
  private DocumentRetrievalManager retrievalManager = null;
  
  private int counter = 0;
  
  private int recordCount = 0;

  private List lstNoBaseRoot = null;
  
  private List lstNoPhysicalFile = null;
  
  private List lstShortcutNoTarget = null;
  
  private List lstDuplicateName = null;
  
  private List lstNoLuceneIndex = null;
  
  private List lstNoLuceneFullText = null;
  
  private List lstMultiLuceneIndex = null;
  
  private List lstProfileMissing = null;
    
  private String resultSummary = null;
  
  private Map resultMessage = null;

  public DmsChecker(SessionContainer sessionContainer, Connection conn) throws Exception {
    this.sessionContainer = sessionContainer;
    this.conn = conn;
  }
  
  /**
   * Begin dms system checking. Prepared program variables and call main action method.
   * @param request
   * @throws Exception
   */
  public void startCheck(HttpServletRequest request) throws Exception {
    this.counter = 0;
    this.recordCount = this.getRecordCountFromDB();
    this.lstNoBaseRoot = new ArrayList();
    this.lstNoPhysicalFile = new ArrayList();
    this.lstShortcutNoTarget = new ArrayList();
    this.lstDuplicateName = new ArrayList();
    this.lstNoLuceneIndex = new ArrayList();
    this.lstNoLuceneFullText = new ArrayList();
    this.lstMultiLuceneIndex = new ArrayList();
    this.lstProfileMissing = new ArrayList();
    this.resultSummary = "";
    this.resultMessage = new HashMap();
    request.getSession().setAttribute(SystemUpdater.DMS_SYSTEM_CHECKER_COUNTER, "0");
    request.getSession().setAttribute(SystemUpdater.DMS_SYSTEM_CHECKER_RECORDCOUNT, String.valueOf(this.recordCount));
    try {
      this.checkingAll(request);
      String summary = "";
      summary += DmsChecker.LIST_NO_BASE_PATH+":"+this.lstNoBaseRoot.size();
      summary += ";";
      this.resultMessage.put(DmsChecker.LIST_NO_BASE_PATH, lstNoBaseRoot);
      summary += DmsChecker.LIST_NO_PHYSICAL_FILE+":"+this.lstNoPhysicalFile.size();
      summary += ";";
      this.resultMessage.put(DmsChecker.LIST_NO_PHYSICAL_FILE, lstNoPhysicalFile);
      summary += DmsChecker.LIST_SHORTCUT_NO_TARGET+":"+this.lstShortcutNoTarget.size();
      summary += ";";
      this.resultMessage.put(DmsChecker.LIST_SHORTCUT_NO_TARGET, lstShortcutNoTarget);
      summary += DmsChecker.LIST_DUPLICATE_NAME+":"+this.lstDuplicateName.size();
      summary += ";";
      this.resultMessage.put(DmsChecker.LIST_DUPLICATE_NAME, lstDuplicateName);
      summary += DmsChecker.LIST_NO_LUCENE_INDEX+":"+this.lstNoLuceneIndex.size();
      summary += ";";
      this.resultMessage.put(DmsChecker.LIST_NO_LUCENE_INDEX, lstNoLuceneIndex);
      summary += DmsChecker.LIST_NO_LUCENE_FULLTEXT+":"+this.lstNoLuceneFullText.size();
      summary += ";";
      this.resultMessage.put(DmsChecker.LIST_NO_LUCENE_FULLTEXT, lstNoLuceneFullText);
      summary += DmsChecker.LIST_MULTI_LUCENE_INDEX+":"+this.lstMultiLuceneIndex.size();
      //summary += ";";
      this.resultMessage.put(DmsChecker.LIST_MULTI_LUCENE_INDEX, lstMultiLuceneIndex);
      //summary += DmsChecker.LIST_PROFILE_MISSING+":"+this.lstProfileMissing.size();
      
      //this.resultMessage.put(DmsChecker.LIST_PROFILE_MISSING, lstProfileMissing);
      this.resultSummary = summary;
      request.getSession().setAttribute(SystemUpdater.DMS_SYSTEM_CHECKER_MESSAGE, this.resultMessage);
      
    }catch (Exception ex) {
      
    }finally {
      if (this.indexManager!=null) indexManager.close();
    }
  }
  
  /**
   * Main method to do checking.
   * @param request
   * @throws Exception
   */
  private void checkingAll(HttpServletRequest request) throws Exception {
    String[] fieldNames = new String[] {
        "D.ID", "D.DOCUMENT_NAME", "D.DOCUMENT_TYPE", "D.ROOT_ID", "D.PARENT_ID",
        "D.RECORD_STATUS", "D.ITEM_STATUS", 
        "V.ID", "V.ITEM_SIZE",  
        "C.ID", "C.CONVERTED_NAME", "C.SEGMENT_NO", "C.EXT"
    };
    String[] fieldTypes = new String[] {
        "Integer", "String", "String", "Integer", "Integer",
        "String", "String", 
        "Integer", "Integer",
        "Integer", "String", "Integer", "String"
    };
    String orderBy = "D.ID ASC, V.ID DESC";
    
    //reindex document (lucene)
    Map basePathCache = new HashMap();
    try {
      
      int pageIndex = 0;
      int pageSize = 100;
      while (true) {
        if (pageIndex*pageSize > recordCount) break;
        
        List lstDocumentIds = new ArrayList();
        for (int i=0; i<pageSize; i++) {
          lstDocumentIds.add(new Integer((pageIndex*pageSize)+i));
        }
        
        Map mapDocuments = this.getDmsDocumentDAO().getMapIdToDVCFieldValueArrayList(lstDocumentIds, fieldNames, fieldTypes, orderBy);
        
        for (int i=0; i<lstDocumentIds.size(); i++) {
          Integer documentID = (Integer)lstDocumentIds.get(i);
          List lstDocuments = (List)mapDocuments.get(documentID);
          if (lstDocuments!=null && !lstDocuments.isEmpty()) {
            Object[] fieldValues = (Object[])lstDocuments.get(0);

            try {
              Integer rootID = (Integer)fieldValues[3];
              
              //List documentDetailList = this.getDmsDocumentDetailDAO().getListByDocumentID(documentID);
              String basePath = (String)basePathCache.get(rootID);
              if (basePath==null) {
                basePath = this.getBasePath(rootID);
                basePathCache.put(rootID, basePath);
              }
              
              this.checkingSingleDocument(fieldValues, basePath);
              
            }catch (Exception ex) {
              log.warn("Document "+(String)fieldValues[1]+"("+fieldValues[0]+") fail to checking.", ex);
            }catch (OutOfMemoryError ex) {
              log.warn("Document "+(String)fieldValues[1]+"("+fieldValues[0]+") fail to checking by out of memory.", ex);
            }
            
          }
          this.counter++;
          request.getSession().setAttribute(SystemUpdater.DMS_SYSTEM_CHECKER_COUNTER, String.valueOf(this.counter));
        }
        pageIndex++;
      }
      
    }catch (Exception ex) {
      log.error(ex, ex);
      throw ex;
    }finally {

    }
  }
  
  /**
   * Get max reocrd count for checking.
   * @return
   * @throws Exception
   */
  private int getRecordCountFromDB() throws Exception {
    PreparedStatement psmt = null;
    ResultSet rs = null;
    try {
      int max = 0;
      String sql = "SELECT max(ID) FROM dms_document";
      psmt = conn.prepareStatement(sql, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
      rs = psmt.executeQuery();
      if (rs.next()) {
        max = rs.getInt(1);
      }
      return max;
    }catch (Exception ex) {
      throw ex;
    }finally {
      try {rs.close();}catch(Exception ignore){}finally {rs=null;}
      try {psmt.close();}catch(Exception ignore){}finally {psmt=null;}
    }
  }
  
  /**
   * Execute a sql to find special document ids.
   * @param sql
   * @return
   * @throws Exception
   */
  private List executeGetDocumentIDSQL(String sql) throws Exception {
    PreparedStatement psmt = null;
    ResultSet rs = null;
    try {
      psmt = conn.prepareStatement(sql, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
      rs = psmt.executeQuery();
      List result = new ArrayList();
      while (rs.next()) {
        result.add(new Integer(rs.getInt(1)));
      }
      return result;
    }catch (Exception ex) {
      throw ex;
    }finally {
      try {rs.close();}catch(Exception ignore){}finally {rs=null;}
      try {psmt.close();}catch(Exception ignore){}finally {psmt=null;}
    }
  }
  
  /**
   * Checking a dms document error, and store it into list variables.   
   * @param fieldValues
   * @param basePath
   * @throws Exception
   */
  private void checkingSingleDocument(Object[] fieldValues, String basePath) throws Exception {  
    Integer documentId = (Integer)fieldValues[0];
    String documentName = (String)fieldValues[1];
    String documentType = (String)fieldValues[2];
    Integer documentParentId = (Integer)fieldValues[4];
    String recordStatus = (String)fieldValues[5];
    String itemStatus = (String)fieldValues[6];
    String convertedName = (String)fieldValues[10];
    Integer segmentNo = (Integer)fieldValues[11];
    String extension = (String)fieldValues[12];
    
    //checking base root
    if (Utility.isEmpty(basePath)) {
      this.lstNoBaseRoot.add(documentId);
    }
    
    //checking physical file
    if (this.isDocumentType(documentType) && !DmsVersion.ARCHIVED_STATUS.equals(itemStatus)) {
      File file = new File(basePath+"/"+(segmentNo!=null ? "Document/segment"+segmentNo.toString()+"/" : "")+convertedName);
      if (!file.exists() || !file.isFile()) {
        this.lstNoPhysicalFile.add(documentId);
      }
    }
    
    //checking shortcut target
    if (DmsDocument.DOCUMENT_LINK.equals(documentType) && GlobalConstant.RECORD_STATUS_ACTIVE.equals(recordStatus)) {
      DmsDocument document = getDocumentRetrievalManager().getRelationshipTargetDocument(documentId, DmsDocument.DOCUMENT_LINK, GlobalConstant.RECORD_STATUS_ACTIVE);
      if (document==null) this.lstShortcutNoTarget.add(documentId);
    }
    
    //checking duplicate name
    if (documentParentId!=null && !(documentParentId.equals(new Integer(0)) && documentName.equals("PERSONAL"))) {
      if (this.checkingDuplicatedName(documentName, documentType, documentParentId)) {
        this.lstDuplicateName.add(documentId);
      }
    }
    
    //checking lucene
    String luceneResult = this.checkingLucene(documentId);
    if (documentParentId!=null && documentParentId.intValue()>0) {
      if (luceneResult.indexOf("N")!=-1) this.lstNoLuceneIndex.add(documentId);
      if (luceneResult.indexOf("D")!=-1) this.lstMultiLuceneIndex.add(documentId);
      if (!Utility.isEmpty(extension) && IndexConverter.isAllowConvert(extension) && this.isDocumentType(documentType)) {
        if (luceneResult.indexOf("C")!=-1) this.lstNoLuceneFullText.add(documentId);
      }
    }
  }
  
  /**
   * Checking special lucene.
   * @param documentID
   * @return
   * @throws Exception
   */
  private String checkingLucene(Integer documentID) throws Exception {
    Hits hits = this.getIndexManager().query(new Term(FieldNames.UID, documentID.toString()));
    String result = "";
    if (hits.length()<1) {
      result += "N";
    }else {
      if (hits.length()>=2) result += "D";
      if (hits.doc(0).get(FieldNames.CONTENT)==null) result += "C";
    }
    return result;
  }
  
  /**
   * Checking if document has duplicate name in system.
   * @param name
   * @param parentID
   * @return
   * @throws Exception
   */
  private boolean checkingDuplicatedName(String name, String type, Integer parentID) throws Exception {
    List result = null;
    if (!DmsDocument.DOCUMENT_LINK.equals(type)) {
      String sql = "SELECT distinct ID FROM dms_document WHERE DOCUMENT_NAME='"+name.replaceAll("'", "''")+"' and PARENT_ID="+parentID+" and DOCUMENT_TYPE<>'"+DmsDocument.DOCUMENT_LINK+"' and (RECORD_STATUS='A' OR RECORD_STATUS='P')";
      result = this.executeGetDocumentIDSQL(sql);
    }else {
      String sql = "SELECT distinct ID FROM dms_document WHERE DOCUMENT_NAME='"+name.replaceAll("'", "''")+"' and PARENT_ID="+parentID+" and DOCUMENT_TYPE='"+DmsDocument.DOCUMENT_LINK+"' and (RECORD_STATUS='A' OR RECORD_STATUS='P')";
      result = this.executeGetDocumentIDSQL(sql);
    }
    if (result!=null && result.size()>1) {
      return true;
    }else {
      return false;
    }
  }

  private String getBasePath(Integer rootID) throws Exception {
    return ((DmsLocMaster) this.getDmsLocMasterDAO().getObjectByID(((DmsRoot)getDmsRootDAO().getObjectByIDIgnoreRecordStatus(rootID)).getLocID())).getLocPath();
  }
  
  public Field getField(String fieldName, String fieldValue) throws Exception {
    return new Field(fieldName, fieldValue, FieldTypes.getStore(fieldName), FieldTypes.getIndex(fieldName), FieldTypes.getTermVector(fieldName));
  }
  
  public Field getField(String fieldName, String typeName, String fieldValue) throws Exception {
    return new Field(fieldName, fieldValue, FieldTypes.getStore(typeName), FieldTypes.getIndex(typeName), FieldTypes.getTermVector(typeName));
  }
  
  
  private boolean isDocumentType(String documentType) throws Exception {
    return (DmsDocument.DOCUMENT_TYPE.equals(documentType) || DmsDocument.COMPOUND_DOC_TYPE.equals(documentType) || DmsDocument.EMAIL_DOC_TYPE.equals(documentType) || DmsDocument.ORIGINSLITY_EMAIL_TYPE.equals(documentType));
  }
  
  private IndexManager getIndexManager() throws IOException {
    if (this.indexManager==null) {
      this.indexManager = new IndexManager(this.sessionContainer, this.conn);
    }
    return this.indexManager;
  }
  
  private DmsDocumentDAObject getDmsDocumentDAO() {
    if (this.dmsDocumentDAO==null) {
      this.dmsDocumentDAO = new DmsDocumentDAObject(this.sessionContainer, this.conn);
    }
    return this.dmsDocumentDAO;
  }

//  private DmsDocumentDetailDAObject getDmsDocumentDetailDAO() {
//    if (this.dmsDocumentDetailDAO==null) {
//      this.dmsDocumentDetailDAO = new DmsDocumentDetailDAObject(this.sessionContainer, this.conn);
//    }
//    return this.dmsDocumentDetailDAO;
//  }
  
  private DmsLocMasterDAObject getDmsLocMasterDAO() {
    if (this.dmsLocMasterDAO==null) {
      this.dmsLocMasterDAO = new DmsLocMasterDAObject(this.sessionContainer, this.conn);
    }
    return this.dmsLocMasterDAO;
  }
  
  private DmsRootDAObject getDmsRootDAO() {
    if (this.dmsRootDAO==null) {
      this.dmsRootDAO = new DmsRootDAObject(this.sessionContainer, this.conn);
    }
    return this.dmsRootDAO;
  }
  
  private DocumentRetrievalManager getDocumentRetrievalManager() {
    if (this.retrievalManager==null) {
      this.retrievalManager = new DocumentRetrievalManager(this.sessionContainer, this.conn);
    }
    return this.retrievalManager;
  }

  public String getResultSummary() {
    return resultSummary;
  }

}
